Stacks
Introduction
A stack is a linear data structure and very
much useful in various applications of computer science. The implementation of
the majority of systems programs is simplified using this data structure.
Before discussing this data structure, let us first consider a few examples of
the stack phenomenon.
Shunting of trains in a railway yard
Suppose there is a railway yard with a single
track. Trains enter into the railway yard for placement, and when they exit, it
is just in opposite order to that they had entered, i.e. the last train comes
out first (see Figure 4.1).
Shipment in a cargo
For the shipment of goods, they are loaded into a
cargo compartment. At the destination, they are unloaded exactly in the
opposite sequence to that in which they were loaded. That is, the goods loaded
last get unloaded first.
Plates on a tray
Suppose a chef placed the dishes on a tray one
above the other. The waiter served the dishes to the customers in the opposite
order that the chef placed them, that is, the dish at the top which
Figure 6.1 Some
examples of stacks.
was placed last by the chef is serviced first. The
first dish placed by the chef on the tray is serviced last by the waiter.
From the
above examples, it is clear that a stack is something which follows the
last-in first-out strategy. This is why a stack is alternatively termed LIFO
(Last-In-First-Out).
Definition
A stack is an ordered collection of
homogeneous data elements where the insertion and deletion operations take
place at one end only.
Like an
array and a linked list, a stack is also a linear data structure but the only
difference is that in the case of the former two, insertion and a deletion
operations can take place at any position. The insertion and deletion
operations in the case of a stack are specially termed PUSH and POP,
respectively, and the position of the stack where these operations are
performed is known as the TOP of the stack. An element in a stack is termed an
ITEM. The maximum number of elements that a stack can accommodate is termed
SIZE. Figure 6.2 shows a typical view of a stack data structure.
Figure 6.2 Schematic
diagram of a stack.
Representation of a Stack
A stack may be represented in the memory in various
ways. There are two main ways: using a one-dimensional array and a single
linked list. Representations of stacks in a memory are discussed in the
following two sections.
Array
Representation of Stacks
First we have to allocate a memory block of
sufficient size to accommodate the full capacity of the stack. Then, starting
from the first location of the memory block, the items of the stack can be
stored in a sequential fashion.
In Figure
6.3(a), Itemi denotes the ith
item in the stack; l and u denote the index range of the array in
use; usually the values of these indices are 1 and SIZE respectively. TOP is a
pointer to point the position of the array up to which it is filled with the
items of the stack. With this representation, the following two ways can be
stated:
EMPTY: TOP < l
FULL: TOP ³ u
Figure 6.3 Two ways of representing stacks.
Linked List
Representation of Stacks
Although array representation of stacks is very
easy and convenient but it allows the representation of only fixed sized
stacks. In several applications, the size of the stack may vary during program
execution. An obvious solution to this problem is to represent a stack using a
linked list. A single linked list structure is sufficient to represent any
stack. Here, the DATA field is for the ITEM, and the LINK field is, as usual,
to point to the next item. Figure 4.3(b) depicts such a stack using a single linked
list.
In the
linked list representation, the first node on the list is the current item that
is the item at the top of the stack and the last node is the node containing
the bottom-most item. Thus, a PUSH operation will add a new node in the front
and a POP operation will remove a node from the front of the list. The SIZE of
the stack is not important here because this representation allows dynamic
stacks instead of static stacks, as with arrays.
In the
linked list representation of a stack, whether a stack is empty or not can be
ascertained by testing the LINK field of the STACK_HEAD node. Note that a test
for overflow is not applicable in this case.
Operations on Stacks
The basic operations required to manipulate a stack are:
PUSH To insert an item into a stack
POP To remove an item from a stack
STATUS To
know the present state of a stack
Let us
define all these operations of a stack. First, we will consider the
above-mentioned operations for a stack represented by an array.
Algorithm Push_Array
Input: The new item ITEM to be pushed
onto it.
Output: A stack with a newly pushed ITEM at the TOP position.
Data structure: An array A with TOP as the pointer.
Steps: 1. If TOP ³ SIZE then 2. Print “Stack is full” 3. Else 4. TOP = TOP + 1 5. A[TOP] = ITEM 6. EndIf 7. Stop |
Here, we
have assumed that the array index varies from 1 to SIZE and TOP points the
location of the current top-most item in the stack. The following algorithm Pop_Array defines the POP of an item from a stack
which is represented using an array A.
Algorithm Pop_Array
Input: A stack with elements.
Output: Removes an ITEM from the top of the stack if it is not
empty.
Data structure: An array A with TOP as the pointer.
Steps: 1. If TOP < 1 then 2. Print “Stack is empty” 3. Else 4. ITEM = A[TOP] 5. TOP = TOP – 1 6. EndIf 7. Stop |
In the following algorithm Status_Array,
we test the various states of a stack such as whether it is full or empty, how
many items are right now in it, and read the current element at the top without
removing it, etc.
Algorithm Status_Array
Input: A stack with elements.
Output: States whether it is empty or full, available free space and
item at TOP.
Data structure: An array A with TOP as the pointer.
Steps: 1. If TOP < 1 then 2. Print “Stack is empty” 3. Else 4. If (TOP ³ SIZE) then 5. Print “Stack is full” 6. Else 7. Print “The element at TOP is”, A[TOP] 8. free = (SIZE – TOP)/SIZE * 100 9. Print “Percentage of free stack is”, free 10. EndIf 11. EndIf 12. Stop |
Now let us see how the same operations can be
defined for a stack represented with a single linked list.
Algorithm Push_LL
Input: ITEM is the item to be inserted.
Output: A single linked list with a newly inserted node with data
content ITEM.
Data structure: A single linked list structure whose pointer to the header
is known from
STACK_HEAD and TOP is the pointer to the first
node.
Steps: 1. new = GetNode(NODE) /* Insert at front
*/ 2. new®DATA = ITEM 3. new®LINK = TOP 4. TOP = new 5. STACK_HEAD®LINK = TOP 6. Stop |
Algorithm Pop_LL
Input: A stack with elements.
Output: The removed item is stored in ITEM.
Data structure: A single linked list structure whose pointer to the header
is known from STACK_HEAD and TOP is the pointer to the first node.
Steps: 1. If TOP = NULL 2. Print “Stack is empty” 3. Exit 4. Else 5. ptr = TOP®LINK 6. ITEM = TOP®DATA 7. STACK_HEAD®LINK = ptr 8. TOP = ptr 9. EndIf 10. Stop |
The operation Status now can be defined as
follows:
Algorithm Status_LL( )
Input: A stack with elements.
Output: Status information such as its state (empty or full), number
of items, item at the TOP.
Data structure: A single linked list structure whose pointer to the header
is known from STACK_HEAD
and TOP is the pointer to the first node.
Steps: 1. ptr = STACK_HEAD®LINK 2. If (ptr = NULL) then 3. Print “Stack is empty” 4. Else 5. nodeCount = 0 6. While (ptr ¹ NULL) do 7. nodeCount = nodeCount + 1 8. ptr = ptr®LINK 9. EndWhile 10. Print “The item at the front is”, TOP®DATA, “Stack contains”, nodeCount, “Number of items” 11. EndIf 12. Stop |
Applications of Stacks
Various applications of stacks are known. A
classical application in a compiler design is the evaluation of arithmetic
expressions; here the compiler uses a stack to translate an input arithmetic
expression into its corresponding object code. Some machines are also known
which use built-in stack hardware called ‘stack machine’. Another important
application of a stack is during the execution of recursive programs; some
programming languages use stacks to run recursive programs. One important
feature of any programming language is the binding of memory variables. Such
binding is determined by the scope rules. There are two scope rules known: the static
scope rule and the dynamic scope rule. Implementation of such scope
rules is possible using a stack known as a run time stack.
The
following subsections highlight the above-mentioned applications of stacks.
Evaluation of
Arithmetic Expressions
An arithmetic expression consists of operands and
operators. Operands are variables or constants and operators are of various
types such as arithmetic unary and binary operators [for example, – (unary), +
(addition), – (subtraction), * (multiplication), / (division), ^
(exponentiation), % (remainder modulo), etc.], relational operators (for
example, <, >, <=, < >, >=, etc.), and Boolean operators
(such as, AND, OR, NOT, XOR, etc.). In addition to these, parentheses such as
‘(’ and ‘)’ are also used. A simple arithmetic expression is cited below:
A
+ B * C / D – E ^ F * G
The
problem to evaluate this expression is the order of evaluation. There are two
ways to fix it. First, we can assign to each operator a precedence and
associativity. For example, a set of usual operators with their precedence and
associativity is given in Table 4.1.
Table
4.1 Precedence and associativity of operators
Operators Precedence Associativity
–
(unary), +(unary), NOT 6 –
^
(exponentiation) 6 Right to left
*
(multiplication), / (division) 5 Left to right
+
(addition), – (subtraction) 4 Left to right
<,
<=, +, < >, >= 3 Left to right
AND 2 Left to right
OR,
XOR 1 Left to right
Thus,
with the above rules of precedence and associativity of operators, the
evaluation will take place for the above-mentioned expression in the sequence
(sequence is according to the number 1, 2, 3, …, etc.)
stated below:
It should
be noted that the above rules for precedence and associativity vary from one
programming language to another.
Another
way of fixing the order of evaluation is parenthesizing the expression fully;
this allows one to override the rules for precedence and associativity. The
following is the parenthesized version of the same expression:
Input: ((A + B) * ((C/D) – (E ^ (F *
G))))
(A fully parenthesized expression)
With this
parenthesization, the innermost parenthesis part
(called sub expression) will be evaluated first, then the next innermost, and
so on; such a sequence of evaluations is shown below:
Whatever way we may specify the order of evaluations,
the problem is that we must scan the expression from left to right repeatedly.
Hence, the above-mentioned processes are inefficient because of the repeated
scanning required. Another problem is the ambiguity about how the compiler can
resolve to generate a correct code for a given expression. The last problem
mainly occurs for a partially parenthesized expression. These problems can be
solved in the following two steps:
1.
Conversion of a given expression into a
special notation
2.
Evaluation/production of an object code
using a stack.
Notations for arithmetic expressions
There are three notations to represent an
arithmetic expression, viz. infix, prefix and postfix (or suffix). The
conventional way of writing an expression is called infix. For example,
A
+ B, C – D, E *
F, G/H, etc.
Here, the notation is
<operand> <operator> <operand>.
This is called infix because the operator
comes in between the operands. The prefix notation, on the other hand,
uses the convention
<operator> <operand> <operand>
Here, the operator come before the operands. The
following are simple expressions in prefix notation:
+AB, –CD, *EF, /GH,
etc.
The prefix notation was introduced by the Polish
mathematician Jan Lukasiewicz and hence also termed Polish notation.
The last
notation is called the postfix (or suffix) notation where the operator
is suffixed by operands:
<operand> <operand> <operator>
The following expressions are in postfix notation:
AB+, CD–, EF*, GH/,
etc.
The postfix notation is just reverse of the Polish
notation, hence it is also termed reversed Polish notation.
It may be
noted that in all of the above notations, a unary operator precedes its
operand.
An expression given in infix notation can easily be
converted into its equivalent prefix or postfix notation. The following rule is
applied to convert an infix expression into a postfix form.
● Assume the fully parenthesized
version of the infix expression.
● Move all operators so that they
replace their corresponding right part of parentheses.
● Remove all parentheses.
The following example illustrates this conversion.
For simplicity, let us consider a fully parenthesized expression.
Input: ((A + ((B ^ C) – D)) * (E – (A/C)))
(A fully parenthesized
expression)
(Arrows point from operators to
their corresponding right parenthesis.)
((A
((B C ^ D – + (E (AC / – *
(Operators are moved to their
respective right parentheses.)
Output: A
B C ^ D – + E A C / – *
(All parentheses are removed yielding the postfix
expression.)
A similar technique can be applied to obtain the
prefix notation for a given infix notation but moving the operators corresponds
to the left parenthesis.
Three notations for the given arithmetic expression
are listed below:
Infix:
((A + ((B ^ C) – D)) * ( E – (A/C)))
Prefix: *
+ A – ^ BCD – E/AC
Postfix: ABC ^ D – + EAC / – *
The following points may be observed from the above three notations:
1.
In both prefix
and postfix equivalents of an infix expression, the variables are in the same
relative positions.
2.
The expressions in prefix or postfix form
are completely parenthesis free.
3.
The operators are rearranged according to
the rules of precedence of operators.
Out of
these three notations, the postfix notation has certain advantages over the
other notations from the computational point of view. The main advantage of
postfix is its evaluation. During the evaluation of an expression in postfix
notation it is no longer required to scan the expression from left to right
several times, but scanning is required exactly once. This is possible using a
stack and will be discussed shortly.
Thus,
evaluation of an expression is a two-step process. First, we have to convert
the expression into its postfix notation, and then evaluate this expression in
postfix notation. In each step, the stack is the main data structure that is
used to accomplish these tasks.
The uses
of the stack for the purpose and the above-mentioned procedures are discussed
below. We will assume the array representation of a stack in our discussions.
Conversion of an infix expression to
postfix expression
To formalize the conversion method, we will assume
simple arithmetic expressions containing the +, –, *, /, and ^ (exponentiation)
operators only (i.e. without unary operators, Boolean operators and relational
operators). The expression may be parenthesized or unparenthesized.
First, we
have to append the symbol ‘)’ as the delimiter at the end of a given infix
expression and initialize the stack with ‘(’. These symbols ensure that either
the input or the stack is exhausted.
Our next
step is iterative: read one input symbol at a time and decide whether it has to
be pushed onto the stack or not. This decision will be governed by Table 4.2.
Table
4.2 In-stack and in-coming priorities of symbols
Symbol In-stack In-coming
priority value priority
value
+ – 2 1
* / 4 3
^ 5 6
operand 8 7
( 0 9
) – 0
From the table, it can be noted that for a symbol
we have considered two priority values, viz. in-stack priority and in-coming
priority values. A symbol will be pushed onto the stack if its in-coming
priority value is greater than the in-stack priority value of the top-most
element. Similarly, a symbol will be popped from the stack if its in-stack
priority value is greater than or equal to the in-coming priority value of the
in-coming element. In order to define the algorithm, we will assume the
following functions:
ReadSymbol( ): From a given infix expression, this will read the next
symbol.
ISP(X): Returns
the in-stack priority value for a symbol X.
ICP(X): This
function returns the in-coming priority value for a symbol X.
Output(X): Append the symbol X
into the resultant expression.
Let as assume that a stack of capacity SIZE is
known and TOP is the current pointer in it. PUSH and POP are usual operations
of the stack.
Algorithm InfixToPostfix
Input: E, simple arithmetic expression in infix notation delimited
at the end by the right parenthesis ‘)’, incoming and in-stack priority values
for all possible symbols in an arithmetic expression.
Output: An arithmetic expression in postfix notation.
Data structure: Array representation of a stack with TOP as the pointer to
the top-most element.
Steps: 1. TOP = 0, PUSH(‘(‘) //
Initialize the stack 2. While (TOP
> 0) do 3. item = E.ReadSymbol( ) //
Scan the next symbol in infix expression 4. x = POP( ) //
Get the next item from the stack 5. Case: item = operand //
If the symbol is an operand 6. PUSH(x) //
The stack will remain same 7. Output(item) //
Add the symbol into the output expression 8. Case: item = ‘)’, //
Scan reaches to its end 9. While x ¹ ‘(’ do //
Till the left match is not found 10. Output(x) 11. x = POP( ) 12. EndWhile 13. Case: ISP(x) ³ ICP(item) 14. While (ISP(x) ³ ICP(item)) do 15. Output(x) 16. x = POP( ) 17. EndWhile 18. PUSH(x) 19. PUSH (item) 20. Case: ISP(x) < ICP(item) 21. PUSH(x) 22. PUSH (item) 23. Otherwise: Print
“Invalid expression” 24. EndWhile 25. Stop |
Note: This is
a procedure irrespective of the type of memory representation of the stack to
convert an infix expression to its postfix form using the basic operations of
the stack, namely PUSH and POP. These operations can be replaced with their
respective versions and hence implementable to stack with any type of memory
representation.
Example 6.1
Let us illustrate the procedure InfixToPostfix
with the following arithmetic expression:
Input: (A +
B) ^ C
– (D * E) / F) (infix
form)
Symbol
reading: 1 2 3 4
5 6
7 8 9 10 11 12 13 14 15 16
Read Stack Output
symbol
Initial (
1 ((
2 (( A
3 ((+ A
4 ((+ AB
5 ( AB+
6 (^ AB+
7 (^ AB +
C
8 ( – AB
+ C ^
9 ( – ( AB
+ C ^
10 ( – ( AB
+ C ^ D
11 ( – ( * AB
+ C ^ D
12 ( – ( * AB
+ C ^ DE
13 ( – AB
+ C ^ DE *
14 ( – / AB
+ C ^ DE *
15 ( – / AB
+ C ^ DE * F
16 AB
+ C ^ DE * F / –
Output: A
B + C ^ DE * F / – (postfix form)
The above procedure assumes that the input infix expressions are
according to the right syntax. So, if the input expression is not correct, its
postfix form will not be correct. Extending the same idea, we can incorporate
relational, Boolean and unary operators in the above procedure.
Evaluation of a
postfix expression
For a given expression in postfix notation, it can
be easily evaluated. The following algorithm EvaluatePostfix
is used to evaluate an arithmetic expression in postfix notation using a
stack.
Algorithm EvaluatePostfix
Input: E, an expression in postfix notation, with values of
the operands appearing in the expression.
Output: Value of the expression.
Data structure: Array representation of a stack with TOP as
the pointer to the top-most element.
Steps: 1. Append a special delimiter ‘#’ at the end of the expression 2. item = E.ReadSymbol( ) //
Read the first symbol from E 3. While (item ¹ ‘#’) do 4. If (item = operand) then 5. PUSH(item) //
Operand is the first push into the stack 6. Else 7. op = item //
The item is an operator 8. y = POP( ) // The
right-most operand of the current operator 9. x = POP( ) //
The left-most operand of the current operator 10. t = x op y // Perform the
operation with operator ‘op’ and operands x, y 11. PUSH(t) //
Push the result into stack 12. EndIf 13. item = E.ReadSymbol(
) //
Read the next item from E 14. EndWhile 15. value = POP( ) //
Get the value of the expression 16. Return(value) 17. Stop |
Example 6.2
To illustrate the algorithm EvaluatePostfix,
let us consider the following expression:
Infix: A + (B * C) / D
Postfix: A B C * D / +
Input: A B C * D / + # with A = 2, B = 3, C =
4, and D = 6
Read symbol Stack
A 2 Push(A = 2)
B 2 3 Push(B = 3)
C 2 3 4 Push(C = 4)
* 2 12 Pop(4), Pop(3), Push(T = 12)
D 2 12 6 Push(D = 6)
/ 2 2 Pop(6), Pop(12), Push(T = 2)
+ 4 Pop(2), Pop(2), Push(T = 4)
# value = Pop( )
Implementation of
Recursion
Recursion is an important tool to describe a
procedure having several repetitions of the same. A procedure is termed recursive
if the procedure is defined by itself. As a simple example, let us consider the
case of calculation of the factorial value for an integer n.
n! = n × (n – 1) × (n – 2) × ···
× 3 × 2 × 1
or
n! = n × (n – 1)!
The last expression is the recursive description of
the factorial whereas the first is the iterative definition. Using a pseudo
code, the above two types of definitions are expressed as follows:
Factorial_I
Input: An integer number N.
Output: The factoral value of N, that is N!.
Remark: Code using the iterative
definition of factorial.
Steps: 1. fact
=1 2. For (i = 1 to N)
do 3. fact = i * fact 4. EndFor 5. Return(fact) //
Return the result 6. Stop |
Here, Step 2 defines the iterative definition for the calculation of a
factorial. Now, let us see the recursive definition of the same.
Factorial_R //Code
using the recursive definition of factorial
Input: An integer number N.
Output: The factoral value of N, that is N!.
Remark: Code using the recursive
definition of factorial.
Steps: 1. If
(N = 0) then //
Termination condition of repetition 2. fact = 1 3. Else 4. fact = N * Factorial_R(N
– 1) 5. EndIf 6. Return(fact) //
Return the result 7. Stop |
Here, Step 4 recursively defines the factorial of
an integer N. This is actually a direct translation of n! = n *
(n – 1)! in the form of Factorial_R
(n) = n * Factorial_R (n –
1). This definition implies that n! will be
calculated if (n – 1)! is known, which in turn
if (n – 2)! is known and so on until n =
0 when it returns 1 (Step 1).
The
question that arises is how this definition can be implemented. We will see
that a data structure stack can be used for this purpose. Some programming
languages such as C, Pascal, which have a dynamic memory management mechanism,
can directly accept the recursive definition of procedures; compilers of these
programming languages are responsible to produce an object code suitable for
execution using a stack (called run time stack). In other programming
languages such as FORTRAN, COBOL, BASIC, which do not have a dynamic memory
management mechanism, it is the user’s responsibility to define and maintain
the stack in order to implement the recursive definition of the procedure.
For an
illustration, let us consider the calculation of 5! using
the recursive definition. To do this, the following steps are needed:
Steps: 1. 5! = 5*4! 2. 4! = 4*3! 3. 3! = 3*2! 4. 2! = 2*1! 5. 1! = 1*0! 6. 0!
= 1 7. 1! = 1 8. 2! = 2 9. 3! = 6 10. 4! = 24 11. 5! = 120 |
Here, it is required to push the intermediate calculations till the
terminal condition is reached. In the above calculation for 5!,
Steps 1 to 6 are the push operations. Then subsequent pop operations will
evaluate the value of intermediate calculations till the stack is exhausted.
To control the recursion, we have to maintain the
following stacks:
Stack(s)
for parameter(s) // To store the parameter(s) with which the recursion is
defined
Stack(s) for local variable(s) // To hold the
local variable(s) that are used within the definition
A stack to hold the return address.
From the
above list of stacks it is evident that the number of stacks required is as
many as there are parameters in the procedure. For some procedure which does
not use any local variable, no stack is required for that purpose. Similarly, a
stack to hold the return address may not be required for some recursive
procedure.
Details
about implementing recursive procedures using a stack are illustrated in the
following sections with some well-known problems. Let us assume that all stacks
are represented using an array structure. Also, assume that the sizes of stacks
are adequate to run a procedure.
We will illustrate the implementation of three
popular recursive computations:
1.
Calculation of factorial value
2. Quick
sort
3. Tower of Hanoi problem
For each problem, we will describe the recursive
description, then the translation of the recursive description to a
non-recursive version using stacks.
Factorial Calculation
Recall that the factorial for an integer N
can be defined recursively as follows:
Factorial(N)
Steps: 1. If (N
= 0) then 2. fact = 1 3. Else 4. fact = N * Factorial(N – 1) 5. EndIf 6. Return (fact) 7. Stop |
To implement the above, we require two stacks: one
for storing the parameter N and another to hold the return address. No
stack is necessary to store local variables, as the procedure does not possess
any local variable. Let these two stacks be PARAM (for parameter) and ADDR (for
return address).
We assume
PUSH(X, Y) operation to push the items X and Y into
the stack PARAM and ADDR, respectively.
Algorithm FactorialWithStack
Input: An integer N, and MAIN, the address of the main
routine, say.
Output: Factorial value of N (that is N!).
Data structure: Array representation of stack.
Steps: 1. val = N, top = 0, addr =
Step 15 2. PUSH (val, addr) // Initialize the stack 3. val = val –
1, addr = Step 11 //
Next value and return address 4. If (val = 0 ) then 5. fact = 1 6. Go to Step 12 7. Else 8. PUSH(val, addr) //
Val pushed into PARAM and addr pushed into
ADDR 9. Go to Step 3 10. EndIf 11. fact = val * fact 12. val = POP_PARAM( ), addr = POP_ADDR( ) 13. Go to addr 14. Return (fact) 15. Stop |
Note that Steps 3–10 control PUSH and Steps 11–13
are POP operations. Here, the stacks are initialized by the calling value and
address of the Return statement, that is, Step 14. POP_PARAM(
) and POP_ADDR( ) are the two POP operations assumed on two stacks PARAM and
ADDR, respectively.
The above
implementation is illustrated for N = 5 in Figure 4.6.
Figure 4.6 Computation
of a factorial (recursively) using a stack.
Class
Stack in Collection Framework
In Java collection framework, the class Vector is a legacy class (see Fig. 6.4). Stack is a subclass of the class Vector that implements a standard last-in, first-out data structure called stack. This means the methods which are available for the Vector class is also available to the Stack class, in addition, it has its own methods.
Figure 6.4: The class hierarchy of class Stack.
Constructor
The class Stack has the single default constructor to create an empty stack.
Methods
The methods of the class Stack are shown in Table 6.1.
Description |
|
boolean empty( ) |
Returns true if
the stack is empty,
and returns false if the stack
contains
elements. |
E peek( ) |
Returns the element on the top of the stack, but does not remove it. |
E pop( ) |
Returns the
element on the
top of the
stack, removing it in the process. |
E push(E element) |
Pushes element onto the
stack. element is also returned. |
int search(Object element) |
Searches for element in
the stack. If found, its offset from the top of
the stack is returned. Otherwise, –1 is returned. |
Table 6.1: The methods defend in Stack class
Creating a stack object and its simple opeartions
Example 6.1 :
Simple stack example with basic operations
import java.util.Stack;
public class StackBasicExample
{
public static void
main(String a[]){
// Creating a
stack of integers
Stack<Integer> stack = new
Stack<>();
System.out.println("Empty stack : "
+ stack);
System.out.println("Empty stack : "
+ stack.isEmpty());
// Following statement will throw an
exception
// if we waint
to pop an item from an empty stack
System.out.println("Empty stack : Pop Operation : " + stack.pop());
// Inserting
some data into the stack created
stack.push(1);
stack.push(22);
stack.push(333);
stack.push(4444);
// Print the entire stack now
System.out.println("Non-Empty stack : " + stack);
System.out.println("Pop operation : " + stack.pop());
System.out.println("After pop Operation : " + stack);
System.out.println("The element at the top : " + stack.peep());
System.out.println("After peep operation : " + stack);
System.out.println("Search operation: " + stack.search(22));
System.out.println("Is stack empty? " + stack.isEmpty());
}
}
Initializing
a stack with an array of object
Example 6.2. From an Array to Stack
loading
This program
illustrates how a Stack object can be created with an array of characters.
import java.util.Stack;
public class ArrayToStackExample
{
public static void
main(String a[]){
// The input
array of expression a+b*c-5
String[] expArray = { “a”, “+”, “b” , “*”, “c”, “-“, “5”};
// Create a
Stack object of type String
Stack<String> stack = new
Stack<String>();
//
Loading the array into stack
for(String s :
expArray){
stack.push(s);
}
System.out.println("The stack : "
+ stack);
}
}
Initializing
a stack with a collection object, say ArrayList of
object
Example 6.3a. From an integer array to Stack
loading
Let
us explore on “How to create a Stack object with a given Int array” here.
import java.util.Stack;
public class ArrayToStackExample
{
public static void
main(String a[]){
Integer[] intArr = { 1001,1002,1003,1004};
Stack<Integer> stack = new
Stack<>();
for(Integer i : intArr){
stack.push(i);
}
System.out.println("Non-Empty stack : " + stack);
}
}
Example 6.3b. From an ArrayList collection to Stack loading
The following program illustrates the initialization of a stack with a given List of integers stored in a collection object of type ArrayList.
import java.util.*;
public class ArrayListToStackExample
{
public static void
main(String a[]){
// Create a
collection object of type ArrayList
ArrayList<Integer>
list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
// Declare a
Stack object
Stack<Integer> stack = new
Stack<Integer>();
// Loading the stack with the
collection items
Stack.addAll(list); // Method is
defined in the Vector class
// Printing the first item in the stack
System.out.println("Top item in the stack : " + stack.peep());
}
}
Copying
a stack to a collection object
Example 6.4. From an ArrayList collection to Stack loading
The following program illustrates the copy of a stack to a collection object of type ArrayList.
import java.util.*;
public class StackBasicExample
{
public static void
main(String a[]){
// The Input stack
Stack<Integer> stack = new
Stack<Integer>();
stack.push(1);
stack.push(2);
stack.push(3);
// Create a
collection object of type ArrayList
ArrayList<Integer>
list = new ArrayList<Integer>();
// Copy the elements in stack to the
collection
list.addAll(stack);
// Printing the Stack object
System.out.println("Non-Empty stack : " + stack);
// Printing the ArrayList
object
System.out.println("Non-Empty List : " + list);
}
}
Example
6.5. Creating a Stack and
Performing basic operations like push, pop and peek
import java.util.*;
public class StackExample
{
public static void
main(String[] args) {
// Creating a Stack
Stack<String>stackOfCards
= new Stack<>();
// Pushing new
items to the Stack
stackOfCards.push("Jack");
stackOfCards.push("Queen");
stackOfCards.push("King");
stackOfCards.push("Ace");
System.out.println("Stack => " + stackOfCards);
System.out.println();
// Popping
items from the Stack
String cardAtTop
= stackOfCards.pop(); // Throws EmptyStackException if the stack is empty
System.out.println("Stack.pop()
=> " + cardAtTop);
System.out.println("Current Stack =>
" + stackOfCards);
System.out.println();
// Get the item at the top of the stack
without removing it
cardAtTop = stackOfCards.peek();
System.out.println("Stack.peek()
=> " + cardAtTop);
System.out.println("Current Stack =>
" + stackOfCards);
}
}
Example
6.6. Other Stack
Operations
·
Check
if the stack is empty.
·
Find
the size of the stack.
·
Search
for an element in the Stack.
import java.util.Stack;
public class StackSizeSearchExample
{
public static void
main(String[] args) {
Stack<String>stackOfCards
= new Stack<>();
stackOfCards.push("Jack");
stackOfCards.push("Queen");
stackOfCards.push("King");
stackOfCards.push("Ace");
System.out.println("Stack : " + stackOfCards);
// Check if the Stack is empty
System.out.println("Is Stack empty? :
" + stackOfCards.isEmpty());
// Find the size of Stack
System.out.println("Size of Stack : "
+ stackOfCards.size());
// Search for an element
// The search()
method returns the 1-based position of the element from the top of the stack
// It returns -1 if the element was not
found in the stack
int position = stackOfCards.search("Queen");
if(position != -1) {
System.out.println("Found the element
\"Queen\" at position : " + position);
} else {
System.out.println("Element not
found");
}
}
}
Example
6.7. The example in this
section shows various ways of iterating over a Stack.
Iterate over a Stack using forEach().
Iterate over a Stack
using iterator().
Iterate over a Stack
using iterator() and
forEachRemaining() method.
Iterate over a Stack
from Top to Bottom using listIterator().
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Stack;
public class IterateOverStackExample
{
public static void
main(String[] args) {
Stack<String>stackOfPlates
= new Stack<>();
stackOfPlates.add("Plate 1");
stackOfPlates.add("Plate 2");
stackOfPlates.add("Plate 3");
stackOfPlates.add("Plate 4");
System.out.println("=== Iterate over a
Stack using Java 8 forEach() method ===");
stackOfPlates.forEach(plate -> {
System.out.println(plate);
});
System.out.println("\n=== Iterate over a
Stack using iterator() ===");
Iterator<String>platesIterator = stackOfPlates.iterator();
while (platesIterator.hasNext()) {
String plate = platesIterator.next();
System.out.println(plate);
}
System.out.println("\n=== Iterate over a
Stack using iterator() and Java 8 forEachRemaining()
method ===");
platesIterator = stackOfPlates.iterator();
while (platesIterator.hasNext()) {
String plate = platesIterator.next();
System.out.println(plate);
}
System.out.println("\n=== Iterate over a
Stack from TOP to BOTTOM using listIterator()
===");
// ListIterator
allows you to traverse in both forward and backward directions.
// We'll start
from the top of the stack and traverse backwards.
ListIterator<String>platesListIterator = stackOfPlates.listIterator(stackOfPlates.size());
while (platesListIterator.hasPrevious()) {
String plate = platesListIterator.previous();
System.out.println(plate);
}
}
}